Skip to content

v2.0: Modernization (M1-M6, 44 tasks)#374

Draft
etr wants to merge 157 commits into
masterfrom
feature/v2.0
Draft

v2.0: Modernization (M1-M6, 44 tasks)#374
etr wants to merge 157 commits into
masterfrom
feature/v2.0

Conversation

@etr
Copy link
Copy Markdown
Owner

@etr etr commented Apr 30, 2026

Summary

Integration branch for the v2.0 modernization effort. Tasks land here individually (one merge commit per task) so the full v2.0 ships as a single reviewable PR.

This PR will remain draft until all milestones are complete.

Milestones

  • M1 — Foundation (TASK-001 … TASK-007): toolchain + build-system prerequisites
  • M2 — Response (TASK-008 … TASK-013)
  • M3 — Request (TASK-014 … TASK-020)
  • M4 — Handlers (TASK-021 … TASK-026)
  • M5 — Routing & Lifecycle (TASK-027 … TASK-036)
  • M6 — Release (TASK-037 … TASK-044)

Specs live under specs/ (product_specs, architecture, tasks).

Merged tasks

  • TASK-001 — Bump C++ standard floor to C++20

Test plan

Per-task validation runs through the groundwork validation loop on each task branch before merging here. Pre-merge of v2.0 to master:

  • ./configure && make clean on macOS (Apple Clang) and Linux (recent GCC)
  • make check green
  • CI matrix green across all supported toolchains
  • No -std=c++(11|14|17) regressions in tree
  • ChangeLog and README reflect v2.0 changes

🤖 Generated with Claude Code

etr and others added 3 commits April 30, 2026 23:24
Raises the project's C++ standard floor from C++17 to C++20 so that
subsequent v2.0 work can rely on concepts, std::span, <bit>,
designated initializers, and std::pmr without per-feature gates.

- m4/ax_cxx_compile_stdcxx.m4: replaced with upstream serial 25
  (autoconf-archive). The vendored serial 12 only accepted [11], [14],
  [17] and m4_fatals on anything else; serial 25 adds [20] and [23]
  alternatives plus the C++20 feature-test bodies.
- configure.ac:47: AX_CXX_COMPILE_STDCXX([17]) -> ([20], [noext],
  [mandatory]). [noext] keeps -std=c++20 (no gnu++20 extensions in
  ABI surface); [mandatory] aborts cleanly on too-old toolchains.
- configure.ac:224: dropped redundant -std=c++17 from the
  --enable-debug AM_CXXFLAGS branch. AX_CXX_COMPILE_STDCXX already
  appends -std=c++20 to $CXX, so leaving the override in would
  silently downgrade debug builds.
- Verified Makefile.am, src/Makefile.am, test/Makefile.am, and
  examples/Makefile.am: no per-subdirectory -std= overrides exist.
- .github/workflows/verify-build.yml:
  - Pruned gcc-9, clang-11, clang-12 matrix rows (incomplete C++20
    support: missing concepts/<bit>/<span> in libstdc++/libc++).
  - Bumped IWYU CXXFLAGS from -std=c++11 to -std=c++20.
- README.md: bumped Requirements to "g++ >= 10 or clang >= 13
  (Apple Clang from Xcode 15+)" and "C++20 or newer". Added a
  one-liner about gcc-toolset-14 on RHEL 9.
- README.CentOS-7: updated to reflect the C++20 floor and the
  gcc-toolset-14 workaround.
- ChangeLog: noted the standard bump under 0.20.0.

Verification (Apple Clang 21 on macOS):
- ./configure && make: succeeds with -std=c++20.
- make check: 17/17 tests pass.
- ./configure --enable-debug && make: clean under
  -Wall -Wextra -Werror -pedantic -std=c++20.
- make check (debug): 17/17 tests pass.
- grep -RE '-std=(c\+\+11|c\+\+14|c\+\+17|gnu\+\+(11|14|17))'
    configure.ac Makefile.am src test  -> zero matches.

Refs: PRD §2 NFR (modern C++ idioms), DR-001.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First task in the v2.0 milestone series (M1-Foundation). Raises the
project's C++ standard floor from C++17 to C++20.
Local planning artifacts from groundwork task scaffolding shouldn't be tracked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.08%. Comparing base (d8b055e) to head (9a8f077).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #374      +/-   ##
==========================================
- Coverage   68.03%   67.08%   -0.96%     
==========================================
  Files          34       30       -4     
  Lines        1730     2579     +849     
  Branches      697     1006     +309     
==========================================
+ Hits         1177     1730     +553     
- Misses         80      186     +106     
- Partials      473      663     +190     
Files with missing lines Coverage Δ
src/create_test_request.cpp 50.00% <ø> (ø)
src/create_webserver.cpp 90.47% <ø> (+2.24%) ⬆️
src/detail/body.cpp 89.06% <ø> (ø)
src/detail/http_endpoint.cpp 60.00% <ø> (ø)
src/http_request.cpp 64.10% <ø> (-4.87%) ⬇️
src/http_response.cpp 81.76% <ø> (+17.76%) ⬆️
src/http_utils.cpp 66.97% <ø> (ø)
src/httpserver/create_test_request.hpp 97.82% <ø> (ø)
src/httpserver/create_webserver.hpp 87.17% <ø> (-9.81%) ⬇️
src/httpserver/detail/body.hpp 93.47% <ø> (ø)
... and 17 more

... and 4 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d8b055e...9a8f077. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

etr and others added 26 commits May 1, 2026 00:49
Tighten the public/private header split so detail headers and the
HTTPSERVER_COMPILATION macro cannot leak to downstream consumers, and
add make-check assertions that protect the surface going forward.

Changes:
  - src/httpserver.hpp: #undef _HTTPSERVER_HPP_INSIDE_ after all child
    includes so the macro does not survive into a consumer's TU.
  - src/Makefile.am: move httpserver/details/http_endpoint.hpp out of
    nobase_include_HEADERS into noinst_HEADERS — distributed in the
    tarball but never installed under $prefix/include. Add
    -DHTTPSERVER_COMPILATION to AM_CPPFLAGS so the lib's own TUs see it.
  - test/Makefile.am: add -DHTTPSERVER_COMPILATION to AM_CPPFLAGS so
    first-party unit tests that legitimately include detail headers
    still compile.
  - configure.ac: stop injecting -DHTTPSERVER_COMPILATION into global
    CXXFLAGS. Scope is now per-directory (lib + tests only); examples
    build as true consumers via <httpserver.hpp>.
  - Makefile.am: new check-headers target with four sub-checks
    (A.1 direct public include must fail, A.2 direct detail include
    must fail, A.3 umbrella must compile cleanly, A.4 post-umbrella
    direct include must still fail) and a new check-install-layout
    target that runs `make install DESTDIR=...` to a stage and asserts
    no `details/` directory or `*_impl.hpp` file leaks. Both wired into
    check-local.
  - test/headers/: four one-line consumer TUs driving the checks.

Per the plan's Phase 3a-i, the detail-header gate stays dual-mode
(_HTTPSERVER_HPP_INSIDE_ || HTTPSERVER_COMPILATION) because
webserver.hpp still transitively includes details/http_endpoint.hpp;
TASK-014's PIMPL split will let a future change tighten that gate to
HTTPSERVER_COMPILATION-only.

Acceptance criteria verified:
  - 17/17 existing tests pass under release and --enable-debug.
  - check-headers A.1 fires with the gate error string.
  - check-install-layout: staged install has no details/ and no
    *_impl.hpp; httpserver.hpp + httpserverpp symlink installed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… for MSYS

TASK-001 raised the C++ floor to C++20, which broke matrix entries
running gcc-10, clang-14, and clang-15 (the autoconf C++20 feature
test rejects them). Drop those entries from extra/none, and bump the
lint and performance jobs (which were pinned to gcc-10) to gcc-14 so
they still exercise an older-but-supported toolchain.

The MSYS native job started failing with "microhttpd.h not found"
because the runner image no longer ships libmicrohttpd transitively.
Add libmicrohttpd-devel to the explicit pacman install line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…set check

libmicrohttpd's <microhttpd.h> hard-asserts that _SYS_TYPES_FD_SET is
defined on Cygwin/MSYS, otherwise emitting `#error Cygwin with winsock
fd_set is not supported`. newlib defines that macro via <sys/select.h>,
included from <sys/types.h> only when __BSD_VISIBLE -- which in turn is
gated on _DEFAULT_SOURCE. Strict ANSI C++ (-std=c++NN, the floor we
adopted in TASK-001 with AX_CXX_COMPILE_STDCXX noext) suppresses
newlib's auto-define of _DEFAULT_SOURCE, so the macro never lands and
microhttpd.h refuses to compile.

This is unrelated to the C++ language mode -- _DEFAULT_SOURCE only
controls feature-test gating in system headers -- so defining it here
preserves DR-001's "noext" portability promise while fixing the build
on every Cygwin/MSYS consumer (not just our CI).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n PPA, revert MSYS libmicrohttpd-devel

Three small follow-ups now that the _DEFAULT_SOURCE Cygwin/MSYS fix has
landed:

1. The four test/headers/consumer_*.cpp gate tests added in TASK-002
   were missing the project's standard LGPL/copyright header, tripping
   the lint job once gcc-14 was running cpplint over them.

2. The "Install Ubuntu test sources" step was running
   add-apt-repository ppa:ubuntu-toolchain-r/test which talks to
   launchpad and has been hitting 504 Gateway Time-out across runs.
   With the C++20 floor we no longer need the toolchain PPA -- gcc-11
   through gcc-14 ship in stock ubuntu-22.04/24.04 repos, and
   clang-13/16-18 likewise. Keep just apt-get update.

3. The earlier "add libmicrohttpd-devel to MSYS pacman" attempt was
   wrong -- there is no such MSYS native package. The actual fix was
   the configure.ac _DEFAULT_SOURCE define landed in 5b78014; revert
   the bogus pacman entry so the install step stops failing first.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduce a new public header `src/httpserver/feature_unavailable.hpp`
defining `class feature_unavailable : public std::runtime_error`. The
constructor takes `(std::string_view feature, std::string_view
build_flag)` and composes a `what()` message that names both, e.g.
`"feature 'tls' unavailable: built without HAVE_GNUTLS"`.

The class is header-only and inline. It has no library dependencies
(only <stdexcept>, <string>, <string_view>), so any TU — including
later tasks like TASK-034 that need to throw it from sites in
build-time-disabled code paths — can include it without circular
header coupling. Keeping it inline also avoids ABI churn for what is
effectively a labelled std::runtime_error and keeps libhttpserver_la
sources untouched.

The header is re-exported from the umbrella `<httpserver.hpp>`
unconditionally (no `#ifdef HAVE_*` wrap): even a build with no
optional features must let consumers name `feature_unavailable` so
they can write `try { ... } catch (const httpserver::feature_unavailable&)`.

The TASK-002 inclusion gate is applied verbatim — direct inclusion of
the header without the umbrella or `HTTPSERVER_COMPILATION` errors
out, and `_HTTPSERVER_HPP_INSIDE_` does not leak post-umbrella (both
verified by the existing check-headers A.1–A.4 recipes).

A new unit test `test/unit/feature_unavailable_test.cpp` provides:
- a TU-scope `static_assert(std::is_base_of_v<std::runtime_error,
  httpserver::feature_unavailable>)` (acceptance criterion 1),
- a test that catches as `std::runtime_error` and asserts both the
  feature name and the build flag appear in `what()` (AC 2),
- a test that catches as the concrete type and confirms it slices to
  `runtime_error` correctly,
- a test with a different (feature, flag) pair to guard against
  hard-coded message text.

Verified locally:
- `make check`: 18/18 PASS (was 17, +1 for feature_unavailable),
- check-headers A.1–A.4 PASS,
- check-install-layout PASS (no details/ leak),
- staged install ships exactly one feature_unavailable.hpp at
  $(prefix)/include/httpserver/feature_unavailable.hpp,
- debug build (--enable-debug, -Werror -Wextra -pedantic) builds and
  tests cleanly.

Refs: PRD-FLG-REQ-004, PRD-FLG-REQ-005; §7 (feature availability).
Introduces a library-defined POD `httpserver::iovec_entry { const void* base;
std::size_t len; }` in a new public header `<httpserver/iovec_entry.hpp>`,
included by `<httpserver/http_response.hpp>` and the umbrella header. The
type replaces POSIX `struct iovec` at the public API surface, keeping
`<sys/uio.h>` out of every public header.

Layout pinning lives in `src/iovec_response.cpp` as six unconditional
static_asserts: three against POSIX `struct iovec` (size + iov_base /
iov_len offsets) per the spec, and three parallel asserts against
libmicrohttpd `MHD_IoVec` because that is the actual cast target on the
dispatch path. The MHD_IoVec asserts are an addition over the spec —
without them the reinterpret_cast bridge is the unsafe one. A TODO
sentinel comment (LIBHTTPSERVER_TODO_TASK004_MEMCPY_FALLBACK) documents
the memcpy fallback strategy that would activate if a divergent-layout
platform ever trips one of the asserts. Today every supported platform
(glibc, musl, macOS, FreeBSD, NetBSD, OpenBSD, illumos) shares the same
layout so the asserts pass and the reinterpret_cast is well-defined.

`iovec_response::get_raw_response()` now builds a contiguous
`std::vector<iovec_entry>` from its owned std::strings and
reinterpret_casts to `const MHD_IoVec*` when calling MHD. This proves
the cast bridge in production code today; TASK-010 will move the same
line into the future `details/body.hpp` factory.

Two new TDD-driven test programs:
- `test/unit/iovec_entry_test.cpp` — verifies POD traits (standard
  layout, trivially copyable), member types, layout equivalence with
  POSIX `struct iovec` from a consumer perspective, and the
  reinterpret_cast bridge round-trip.
- `test/unit/header_hygiene_iovec_test.cpp` — declares a colliding
  `struct iovec` before including `iovec_entry.hpp` directly. The TU
  compiling at all proves the new public header pulls in nothing from
  `<sys/uio.h>`. (The broader umbrella-leak concern — current umbrella
  transitively pulls `<sys/uio.h>` via gnutls and `<sys/socket.h>` —
  is out of scope for TASK-004 and is the remit of TASK-007's
  header-hygiene CI gate.)

Build: 20/20 tests pass under both default and `--enable-debug`
(-Wall -Wextra -Werror -pedantic -O0). `grep -E '#include\s+<sys/uio\.h>'
src/httpserver/*.hpp` returns no results. `make install` ships the new
header at `$prefix/include/httpserver/iovec_entry.hpp`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…vements

- Delete copy constructor and copy assignment on iovec_response to close
  CWE-416 use-after-free: the owning constructor stores entries_ as raw
  void* into owned_buffers_ strings; a defaulted copy would shallow-copy
  entries_ while deep-copying owned_buffers_ to new addresses, making
  entries_ dangle after source destruction. Move semantics are safe and
  kept. Static asserts in iovec_response_test.cpp guard this invariant.

- Remove the spurious '#include "httpserver/iovec_entry.hpp"' from
  http_response.hpp; http_response itself never uses iovec_entry, and
  iovec_response.hpp already includes it directly.

- Add @attention Doxygen contract to the non-owning iovec_response
  constructor documenting that caller buffers must outlive MHD_destroy_response.

- Remove duplicate offsetof/sizeof/alignof layout-pinning static_asserts
  from iovec_entry_test.cpp; authoritative copies live in iovec_response.cpp
  where the reinterpret_cast actually occurs.

- Add iovec_response_test.cpp (was untracked) with content-type forwarding
  tests and move-semantics tests for both constructor variants.

- Commit iovec_response.hpp, iovec_response.cpp, and test/Makefile.am that
  were modified/added in iter-1 but never staged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces the type-safe HTTP-method primitives that http_resource,
the route table, and lambda registration will consume.

- enum class http_method : std::uint8_t { get, head, post, put, del,
  connect, options, trace, patch, count_ }. Identifier `del` avoids
  the C++ keyword; wire token returned by to_string is "DELETE".
- struct method_set { std::uint32_t bits = 0; } with constexpr
  contains/set/clear/set_all/clear_all and defaulted operator==.
- Free constexpr noexcept bitwise operators (|, &, ^, ~, |=, &=, ^=)
  on http_method and method_set, including mixed (set, enum) overloads.
  All operators usable in constant expressions and at runtime ("consteval-
  friendly" without forbidding runtime use, which the route-table writer
  path needs).
- to_string(http_method) returning std::string_view for logging and
  the 405 Allow: header. Total over the 9 enumerators; out-of-range
  returns an empty view so logging stays robust against stale values.
- Layout/width invariants pinned at namespace scope:
  count_ <= 32, standard layout, trivially copyable,
  sizeof(method_set) == sizeof(uint32_t).
- Re-exported from <httpserver.hpp> and installed via
  nobase_include_HEADERS in src/Makefile.am.
- Test driver test/unit/http_method_test.cpp covers both compile-time
  static_asserts (round-trip, layout, bitwise composition, complement
  bounding, to_string totality) and 13 runtime LT_BEGIN_AUTO_TEST
  cases including a contract check that to_string matches
  libmicrohttpd's MHD_HTTP_METHOD_* tokens.

All 22 testsuite entries pass under the default build and under
--enable-debug (-Wall -Wextra -Werror -pedantic).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move every value-form #define from public headers into
inline constexpr declarations under httpserver::constants:

- DEFAULT_WS_PORT      -> std::uint16_t (9898)
- DEFAULT_WS_TIMEOUT   -> int           (180 seconds)
- DEFAULT_MASK_VALUE   -> std::uint16_t (0xFFFF)
- NOT_FOUND_ERROR      -> std::string_view ("Not Found")
- METHOD_ERROR         -> std::string_view ("Method not Allowed")
- NOT_METHOD_ERROR     -> std::string_view ("Method not Acceptable")
- GENERIC_ERROR        -> std::string_view ("Internal Error")

The new header src/httpserver/constants.hpp uses the established
two-token gate (_HTTPSERVER_HPP_INSIDE_ + HTTPSERVER_COMPILATION),
is re-exported from <httpserver.hpp>, and is registered in
nobase_include_HEADERS so it ships in the install layout.

Internal callers in webserver.cpp, http_utils.cpp,
create_webserver.hpp, and http_utils.hpp are migrated to the
namespaced names. The string_response call sites materialize a
std::string from the string_view to satisfy the existing ctor
signature.

A new unit test (test/unit/constants_test.cpp) pins the values
and types via static_assert, and uses #ifdef sentinels to
witness that the v1 macro names no longer leak into consumer
namespace after #include <httpserver.hpp>.

NOT_METHOD_ERROR has no in-tree caller; retained for v1 API
parity per the v2.0 mechanical-migration policy.

Acceptance:
- 23/23 tests pass (release + debug -Werror -Wall -Wextra)
- Filtered grep on src/httpserver/*.hpp shows no leftover
  value-constant #defines (include guards, _WINDOWS,
  _WIN32_WINNT, and COMPARATOR are out of scope per plan §2)
- Installed-header layout includes httpserver/constants.hpp

Closes TASK-006.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mark all five action items complete and set task status to Complete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update specs/tasks/_index.md to change TASK-006 status from 'In Progress'
to 'Done', matching the completed state in TASK-006.md and the pattern
used by TASK-003, TASK-004, and TASK-005.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a two-layer header-hygiene gate that locks in the "no backend
headers leak through <httpserver.hpp>" invariant from PRD-HDR-REQ-001..003.

Layer 1 -- compile/runtime sentinel (test/unit/header_hygiene_test.cpp):
  Includes only <httpserver.hpp>, then checks well-known include-guard
  macros (MHD_VERSION, _PTHREAD_H{,_}, GNUTLS_GNUTLS_H, _SYS_SOCKET_H{,_},
  _SYS_UIO_H{,_}). At runtime it prints the leaked headers and exits 1.
  Per-target CPPFLAGS overrides AM_CPPFLAGS so HTTPSERVER_COMPILATION
  and the build-tree -I src/httpserver/ entries are NOT in scope --
  mimics a real consumer translation unit.

Layer 2 -- preprocessor grep against staged install (`make check-hygiene`):
  Stages `make install DESTDIR=$(CHECK_HYGIENE_STAGE)` to a clean tree,
  preprocesses test/headers/consumer_umbrella_no_backend.cpp using ONLY
  -I$(CHECK_HYGIENE_STAGE)$(includedir), then greps cpp line markers
  for forbidden backend headers. HEADER_HYGIENE_STRICT controls
  fatality (default no -> informational; yes -> hard fail at TASK-020).

Both gates are wired into `make check`:
- header_hygiene runs as a check_PROGRAMS test, marked XFAIL_TESTS
  until M5 lands and the umbrella is clean. Automake's XPASS-as-error
  default is the explicit signal for TASK-020 to remove the marker.
- check-hygiene runs via check-local; in non-strict mode it prints an
  EXPECTED-FAIL banner with diagnostics and exits 0 so `make check`
  stays green during M2-M5 while keeping leak progress visible.

CI surface: new header-hygiene matrix entry in verify-build.yml runs
`make check-hygiene` as a focused, named GitHub Actions check.

TASK-020.md updated with explicit M5 close-out steps (delete
XFAIL_TESTS line + flip HEADER_HYGIENE_STRICT default).

Verified locally on macOS/aarch64 with gnutls 3.x, libmicrohttpd 1.0.5,
Apple Clang 15+: 24 tests / 23 PASS / 1 XFAIL (header_hygiene); the
sentinel correctly reports microhttpd, pthread, gnutls, sys/socket,
sys/uio leaks; check-hygiene reports EXPECTED-FAIL on staged install
(webserver.hpp still references private detail header until TASK-014).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… hygiene CI matrix

- check-local: build one DESTDIR=.shared-check-stage and pass it to both
  check-install-layout and check-hygiene via CHECK_*_SHARED=yes, halving
  the install cost of `make check`. Standalone invocations still do their
  own install.
- check-hygiene: gate the staged install behind a $(HYGIENE_STAMP) mtime
  sentinel so repeated standalone runs are no-ops when public headers
  haven't changed; bypassed when CHECK_HYGIENE_SHARED=yes.
- check-hygiene grep: anchor HEADER_HYGIENE_FORBIDDEN to a leading "/"
  so leak detection only matches absolute paths, not arbitrary substrings.
- clean-local: remove the stage directories on `make clean`.
- CI: header-hygiene matrix entry skips the unconditional `make check`
  step (the dedicated `make check-hygiene` step is the gate for that job).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the polymorphic body hierarchy that http_response's SBO buffer will
host (TASK-009) and the public body_kind enum that http_response::kind()
will return (TASK-011). TASK-008 ships only the standalone hierarchy:
each subclass is independently constructible, destructible, and
materializable, mirroring the corresponding v1 *_response::get_raw_response.

New public header (umbrella-included):
- httpserver/body_kind.hpp: enum class body_kind : std::uint8_t {
  empty, string, file, iovec, pipe, deferred }; empty=0 so a
  value-initialised body_kind matches the no-body state.

New private header (HTTPSERVER_COMPILATION-only, never installed):
- httpserver/details/body.hpp: abstract detail::body + 6 final
  subclasses (empty_body, string_body, file_body, iovec_body, pipe_body,
  deferred_body) plus per-subclass static_assert(sizeof <= 64) and
  static_assert(alignof(deferred_body) <= 16) for the SBO budget
  (DR-005).

Out-of-line definitions in src/details/body.cpp:
- materialize() per subclass mirrors v1 byte-for-byte
  (string=PERSISTENT, file=open/fstat/lseek/from_fd, iovec=CWE-190
  guard + reinterpret_cast to MHD_IoVec, pipe=from_pipe, deferred=
  from_callback with a static trampoline).
- Layout-pinning static_asserts duplicated from iovec_response.cpp
  (TASK-013 will remove the originals).
- pipe_body::~pipe_body() closes fd_ only if materialize() was never
  called (MHD owns it after a successful materialise).

New test:
- test/unit/body_test.cpp drives every subclass through MHD's
  daemon-independent inspection APIs (no daemon spun up). 12 tests, 29
  checks; the deferred trampoline is exposed as a public static so it
  can be unit-tested directly. Linked with explicit -lmicrohttpd
  (mirrors uri_log).

Observed sizes on libc++/arm64: empty=16, string=32, file=40, iovec=40,
pipe=16, deferred=40. All well under the 64 B SBO budget — TASK-010
will not need the heap-fallback branch on supported toolchains.

Out of scope (TASK-009/010): http_response wiring, body_inline_
fallback, kind() accessor, removal of v1 *_response subclasses.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Applies fixes from the iter1 review pass on the detail::body hierarchy:

file_body (CWE-367 / perf):
- Open + fstat moved to constructor; size() is now accurate immediately.
- Drops lseek(SEEK_END); materialize() uses st_size from fstat.
  Closes the TOCTOU window between size discovery and the fd handed
  to MHD_create_response_from_fd, and removes the side-effect on the
  fd's read position.
- Adds destructor that closes fd_ only when MHD never took ownership
  (materialized_ stays false until from_fd returns non-null).

deferred_body (CWE-476):
- trampoline() guards against null cls and empty producer_ before
  invoking the std::function. MHD's callback path doesn't catch C++
  exceptions, so a bad_function_call would terminate in MHD's IO
  thread; the guard returns MHD_CONTENT_READER_END_WITH_ERROR instead.
- Constructor asserts producer_ is non-empty (debug-only precondition).

Header docs:
- file_body: documents path-canonicalisation contract (O_NOFOLLOW only
  blocks the final component) and fd ownership lifecycle.
- iovec_body: documents the borrowed-pointer lifetime contract
  (iov_base buffers must outlive the MHD_Response*) and the heap
  allocation note from DR-005.
- deferred_body: documents the std::function SBO caveat — capturing
  more than the implementation-defined threshold silently heap-allocates.

Tests:
- file_body_size_known_before_materialize: size() must be correct at
  construction (21 bytes for test_content), not only after materialize.
- deferred_body_trampoline_null_cls_returns_error: trampoline with
  cls==nullptr returns MHD_CONTENT_READER_END_WITH_ERROR rather than
  dereferencing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings in the polymorphic detail::body hierarchy plus iter1 review-pass
fixes (file_body TOCTOU, deferred_body null-callable guard, header
lifetime/ownership docs, and accompanying tests).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sweeps in groundwork-generated planning content that had been left
untracked across recent task work, and adds .DS_Store to .gitignore so
macOS metadata stops appearing as untracked.

Planning content:
- specs/product_specs.md — top-level product spec.
- specs/architecture/ — system overview, architectural drivers,
  per-component specs (body-hierarchy, create-webserver, http-method,
  http-request, http-resource, http-response, route-table, webserver,
  websocket-handler), cross-cutting concerns, integration, feature
  availability, build/packaging, testing, observability, the DR-001..011
  decision records, open questions, documentation, and appendices.
- specs/tasks/M{1..6}-*/TASK-*.md — task definitions for the v2.0
  milestones (M1 foundation through M6 release). Pre-existing tasks
  TASK-006/007 were already tracked from prior commits; this adds the
  rest, including the M2 response, M3 request, M4 handlers, and M5
  routing-lifecycle definitions.

Review records:
- specs/unworked_review_issues/2026-04-30..2026-05-03_*.md — outputs
  from the iter1 review passes on TASK-001 through TASK-008. Captured
  for traceability; "unworked" denotes issues not yet folded back into
  task scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When TASK-003..008 were merged into feature/v2.0 they were not pushed
individually, so the cumulative push surfaced regressions across the
matrix. This sweeps them up.

Build error (basic ubuntu / valgrind / windows-IWYU):
- test/unit/body_test.cpp:56-60: static_cast<int>(uint8_t-enum) >= 0
  is always-true, breaking -Werror=type-limits. Replace with
  enumerator != body_kind::empty so the compile-time reference still
  guards against a missing enumerator without the bogus comparison.

cpplint (17 errors → 0):
- Include order:
  - src/details/body.cpp, src/iovec_response.cpp,
    src/httpserver/details/body.hpp,
    test/unit/{body_test,header_hygiene_test,http_method_test,
    iovec_entry_test}.cpp: move <microhttpd.h> and <sys/uio.h> into
    the C-system-header group so the layout is primary, c, c++, other.
- Missing includes:
  - src/details/body.cpp, src/iovec_response.cpp: add <string> for
    std::string in the file_body / iovec_response signatures.
  - src/iovec_response.cpp: add <utility> for std::move.
- Header guard:
  - src/httpserver/details/body.hpp: cpplint expects #ifndef GUARD as
    the first non-comment line. Move the SRC_HTTPSERVER_DETAILS_BODY_HPP_
    guard above the HTTPSERVER_COMPILATION #error block (which now
    lives inside the guard).
- Misc:
  - body_kind.hpp: NOLINT(build/include_what_you_use) on the `string`
    enumerator (cpplint mistook it for std::string).
  - body_test.cpp:251: split single-line if-with-multiple-statements.
  - http_method_test.cpp:121: add space between [] and { in lambda.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
etr and others added 30 commits May 12, 2026 00:03
…ings recorded)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collapse 17 paired `foo()/no_foo()` setters into single
`foo(bool enable = true)` setters on create_webserver, rename the
remaining one-way negative flags (`no_listen_socket`, `no_thread_safety`,
`no_alpn`) to their positive counterparts (`listen_socket`,
`thread_safety`, `alpn`), and add setter-time validation that throws
`std::invalid_argument` with a parameter-named message:

- port: int overload validates [0, 65535]; uint16_t overload preserved
- max_threads, max_connections, memory_limit, connection_timeout,
  per_IP_connection_limit, max_thread_stack_size, nonce_nc_size,
  listen_backlog, address_reuse, tcp_fastopen_queue_size: reject < 0
- client_discipline_level: reject < -1 (-1 is the unset sentinel)
- file_upload_dir: reject empty string
- bind_address(string): error message now prefixed with "bind_address:"

Header collapsed from 593 lines (feature/v2.0) / 554 lines (master
baseline) to 253 lines — 54% under the v1 baseline (PRD §3.3 target
was 30%). Acceptance grep
`grep -E '^\s*create_webserver& no_' src/httpserver/create_webserver.hpp`
now returns empty. Internal `webserver`-side field names
(`no_listen_socket`, `no_thread_safety`, `no_alpn`) are unchanged so
`webserver.cpp` does not churn — only the public API is renamed.

Tests: new unit tests in test/unit/create_webserver_test.cpp cover
port-out-of-range, every numeric validator, file_upload_dir empty,
bind_address parameter-name message, and the bool-arg shape for every
toggled flag (including the renamed listen_socket/thread_safety/alpn
and widened tcp_nodelay/turbo/suppress_date_header/sigpipe_handled_by_app).
77 tests, 81 checks pass. Full `make check` green (42/42).

Internal callers rewritten: test/integ/ws_start_stop.cpp,
test/integ/file_upload.cpp, test/unit/routing_regression_test.cpp,
examples/file_upload.cpp, examples/external_event_loop.cpp (comment).
README.md updated to document the bool-arg public API.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ings recorded)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove #ifdef HAVE_BAUTH/HAVE_DAUTH/HAVE_GNUTLS/HAVE_WEBSOCKET guards
from public headers. Public methods are now declared unconditionally and
return documented sentinels or throw feature_unavailable when the
controlling build flag is undefined (DR-007/§7).

Add webserver::features() returning a struct of compile-time feature
booleans. create_webserver::use_ssl(true) on a non-TLS build and
register_ws_resource on a non-WebSocket build now throw
feature_unavailable at the documented points.

Acceptance: grep '#if(def)? HAVE_(BAUTH|DAUTH|GNUTLS|WEBSOCKET)'
src/httpserver/*.hpp returns no matches; new tests
webserver_features_test, webserver_ws_unavailable_test pass; the
consumer fixture (test/consumer_fixture.cpp) compiles unchanged
across configurations.

Refs: PRD-FLG-REQ-001..004, DR-007.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t digest auth) and housekeeping (status Done in _index.md)

- Add HAVE_DAUTH guard so digest_auth(true) on a build without digest support throws feature_unavailable.
- Guard http_request::check_digest_auth against null underlying connection (test-request path).
- Add test/unit/webserver_dauth_unavailable_test.cpp covering the new throw site.
- Update specs/tasks/_index.md: TASK-034 status -> Done.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Capture the iter-2 validation review findings that were intentionally left
unworked — the iter-2 pass approved across all 8 active agents and none of
the remaining findings were blocking. Persisted via the standard
persist-unworked-findings.js helper.
Mirror TASK-023's ownership pattern on the websocket-registration
surface (PRD-HDL-REQ-003, PRD-HDL-REQ-005, DR-010).

Public API changes (webserver.hpp):
- Removed: bool register_ws_resource(string, websocket_handler*).
- Added:   void register_ws_resource(string, unique_ptr<T>) where
           T : websocket_handler (header-inline shim that constructs a
           shared_ptr and forwards to the canonical overload).
- Added:   void register_ws_resource(string, shared_ptr<websocket_handler>).
- Added:   void unregister_ws_resource(string).
- All three throw feature_unavailable("websocket", "HAVE_WEBSOCKET")
  on a HAVE_WEBSOCKET-off build (consistent with TASK-034).

Internal changes (webserver_impl.hpp, webserver.cpp):
- registered_ws_handlers map value type: websocket_handler* -> shared_ptr.
- ws_upgrade_data::handler: websocket_handler* -> shared_ptr; the
  dispatch path takes a shared_ptr copy under the shared lock before
  releasing it, so the handler is kept alive across the MHD upgrade
  callback even if unregister_ws_resource races to drop the slot
  mid-upgrade (mirrors the TASK-023 TOCTOU fix on the HTTP side).
- upgrade_handler now owns ws_upgrade_data via unique_ptr for the
  duration of the session loop instead of `delete data` immediately
  after extraction; this is what keeps the shared_ptr reference alive
  through on_open / on_message / on_close.
- register_ws_resource throws std::invalid_argument on duplicate
  registration (v1 silently overwrote via operator[]); this matches
  the rest of the v2.0 registration surface.

Tests:
- New: test/unit/webserver_register_ws_smartptr_test.cpp. Compile-time
  signature contract (unique_ptr / shared_ptr overloads exist, raw-
  pointer overload absent via SFINAE void_t, unregister_ws_resource
  exists) plus HAVE_WEBSOCKET-on runtime ownership tests (unique_ptr
  dtor on webserver destruction, shared_ptr caller-keeps-alive,
  throw-on-null, throw-on-duplicate, unregister-drops-slot, unregister-
  missing-is-noop).
- Updated: test/unit/webserver_ws_unavailable_test.cpp. Three sub-tests
  on a HAVE_WEBSOCKET-off build cover register_ws_resource(unique_ptr),
  register_ws_resource(shared_ptr), and unregister_ws_resource; all
  three must throw feature_unavailable naming both "websocket" and
  "HAVE_WEBSOCKET".
- Updated: test/consumer_fixture.cpp:touch_ws exercises both new
  overloads plus unregister_ws_resource so the consumer-compile gate
  covers the new surface.

Callsite migration:
- examples/websocket_echo.cpp: now uses make_unique<echo_handler>().

Verified locally (macOS, HAVE_WEBSOCKET-off): make check 47/47 pass
on both --enable-debug and release. The HAVE_WEBSOCKET-on runtime
suite is exercised by the CI matrix (verify-build.yml).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire the new handler-return-by-value contract end-to-end through the
dispatch path. http_resource::render_* virtuals now return http_response
by value (DR-004, PRD-RSP-REQ-007); detail::lambda_resource::invoke_
drops the v1 make_shared<http_response> shim and forwards the prvalue
directly; webserver_impl::finalize_answer's dispatch site moves the
returned value into modded_request::response_ — a new
std::optional<http_response> slot that anchors the deferred body's
lifetime to ~modded_request() (and therefore to MHD's
request_completed), satisfying DR-010 verbatim.

Synth-response helpers (not_found_page, method_not_allowed_page,
internal_error_page, run_internal_error_handler_safely) return
http_response by value. materialize_response and
get_raw_response_with_fallback take http_response* / modded_request*
through the optional. detail::empty_render and src/http_resource.cpp
are removed; the default render() inlines a default-constructed
http_response{} (status_code_ == -1 sentinel) routed through
internal_error_page as before.

The auth_handler_ptr signature (shared_ptr<http_response>) is
intentionally preserved — its migration is out of TASK-036 scope; the
dispatch path dereferences and moves into the optional.

TASK-035's websocket_handler shared_ptr / unique_ptr overloads are
also preserved verbatim — they are a separate API surface (DR-010
Option 1 for websocket handler ownership).

Tests:
- test/unit/http_resource_test.cpp: ten static_asserts pin the
  by-value return type of every render_* virtual at compile time.
- test/integ/deferred.cpp: three new acceptance tests cover
  AC-1 (on_get lambda by-value -> 200/body), AC-2 (http_resource
  subclass render_get by-value -> 200/body), and AC-3
  (destruction_sentinel inside the producer's captures is destroyed
  exactly when request_completed fires, proving the producer state
  outlives every MHD trampoline invocation).
- All 47 tests in the suite pass.

Related: PRD-HDL-REQ-001, PRD-RSP-REQ-007, DR-004, DR-010, §5.3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-001, PRD-RSP-REQ-007)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires a two-lane GitHub Actions matrix that locks in the "same consumer
source compiles against every HAVE_* combination" invariant introduced
by TASK-034. PRD-FLG-REQ-001 / arch §9 testing item 2.

Changes:
- test/consumer_fixture.cpp: extend the existing TASK-034 fixture to
  pin every remaining TLS cert accessor (get_client_cert_{issuer_dn,
  cn,fingerprint_sha256,not_before,not_after}, has_client_certificate,
  is_client_cert_verified), the DAUTH check_digest_auth /
  check_digest_auth_digest declarations via member-function pointers
  (so the OFF lane links without invoking a method that would throw
  on a !HAVE_DAUTH build), and the positive-`true` form of the three
  feature-flag setters use_ssl / basic_auth / digest_auth on
  create_webserver (again as member-function pointers).
- .github/workflows/verify-build.yml: add two matrix entries,
  flag-invariance-on (stock libmicrohttpd + gnutls) and
  flag-invariance-off (libmicrohttpd rebuilt with --disable-bauth
  --disable-dauth --disable-websockets and gnutls install step
  skipped so all four HAVE_* auto-detect to off). Each lane runs
  `make consumer_fixture`; broad `make check`/cppcheck are skipped
  since the gate is compile+link-only. Adds two verification steps
  asserting (a) libmicrohttpd has the BAUTH/DAUTH symbols stripped
  and libmicrohttpd_ws is absent, and (b) libhttpserver's configure
  reported every HAVE_* as 'no' in the OFF lane.
- test/Makefile.am: cross-reference TASK-037 in the consumer_fixture
  banner.
- specs/tasks/_index.md, specs/tasks/M6-release/TASK-037.md: status
  Not Started -> Done; action items checked off.

Local RED proof (not committed): wrapping get_client_cert_dn() in
#ifdef HAVE_GNUTLS and compiling consumer_fixture.cpp with -UHAVE_GNUTLS
fails with "no member named 'get_client_cert_dn'", confirming the gate
catches the documented regression.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…56 pin libmicrohttpd downloads), correctness (make -C test consumer_fixture so CI builds in build/test/), and coverage (pin remaining unconditional TLS credential-material setters in consumer_fixture).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tizer-flag fix

Adds test/unit/http_response_move_sanitizer_test.cpp — the v2.0
sanitizer canary for http_response move ops (DR-005, AR-004,
PRD-RSP-REQ-001 / PRD-RSP-REQ-007). Complements the existing whitebox
SBO test (http_response_sbo_test.cpp) by:

  * Driving all four move-assign cases plus both move-ctor cases via
    factory-constructed responses (string / empty / file), exercising
    the public API rather than placement-new'ing bodies through the
    SBO friend hook.
  * Covering the heap-fallback branch in adopt_body_from /
    destroy_body with a synthetic >64 B body subclass (fat_body) —
    no production body currently exceeds the SBO budget, so this is
    the first test that genuinely exercises the heap-pointer-swap
    path. fat_body is placed through the same friend hook the SBO
    test uses; no production-API widening.
  * Pinning the moved-from invariant contract: a moved-from
    http_response is destructible, accessor-safe (get_status / kind
    / get_header / get_headers().size() etc. are well-defined), and
    re-assignable (a fresh response can be move-assigned into it).
  * Adding a file_body move-ctor case so the hand-written fd-ownership
    transfer in file_body is sanitizer-exercised.

Each cycle is non-tautological — verified by injecting deliberate
bugs into adopt_body_from (force inline branch on heap path) and
destroy_body (skip ~body on heap path) and confirming the
corresponding heap-path assertions and dtor-counter checks fired.
Implementation restored after RED verification.

Also fixes a long-standing typo in .github/workflows/verify-build.yml
(lines 656-660): `CXXLAGS` -> `CXXFLAGS` across the asan / msan /
lsan / tsan / ubsan lanes, plus a duplicated `export export` on the
ubsan line. Before this fix, autoconf did not honour the C++ flag
(CXXLAGS is not a recognized variable), so the sanitizer runtime
libs were linked but C++ TUs were compiled without -fsanitize=...
The matrix names were unchanged but the lanes were no-ops on the
library code. The fix is invisible at the matrix-surface level; any
sanitizer findings in unrelated code after this commit are
pre-existing issues the CI was silently letting through, not
regressions introduced here.

Local verification:
  * make check (debug build, all 48 testsuite entries): PASS
  * ASan rebuild + http_response_move_sanitizer: 60/60 checks PASS
  * UBSan rebuild + http_response_move_sanitizer: 60/60 checks PASS
  * cpplint: clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ings (1 major, 27 minor).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s (PRD-RSP-REQ-001, PRD-RSP-REQ-007, arch §9 testing item 3)
…p_resource)

Adds a `make bench` target (separate from `make check`) that runs two
microbenchmarks verifying the two PRD §3.6 numeric acceptance criteria:

* bench_get_headers          -- PRD-REQ-REQ-001: v2.0 get_headers() is
                                >=10x faster than v1's. Measured: ~230x
                                on the maintainer reference host.

* bench_sizeof_http_resource -- PRD-REQ-REQ-003: sizeof(http_resource)
                                shrunk by ~the empty std::map footprint
                                after the method_set refactor. Pure
                                compile-time static_assert.

The v1 baseline literals live in test/v1_baseline/v1_constants.hpp,
sampled once on master (d8b055e) and committed with the host metadata
that produced them. test/v1_baseline/measure_v1_sizes.cpp and
measure_v1_get_headers.cpp ship in EXTRA_DIST as the re-measurement
recipe; they are not built by `make bench` or `make check`. The full
methodology (warmup, 10x1M timing loop with steady_clock + asm-volatile
sink, libstdc++ vs libc++ caveats) is documented in
test/PERFORMANCE.md, alongside the rationale for keeping bench out of
the sanitizer-gated CI matrix.

Notes on the static_assert algebra: TASK-039's literal task wording
suggests `sizeof(http_resource) <= V1_SIZEOF - V1_MAP_SIZEOF`. That
formula fails on every stdlib because v2.0 added a small method_set
member to replace the map. The committed assertion encodes the
mathematically correct contract -- "the removed map saved at least
its empty footprint, less the new method_set field" -- and a tighter
strict-shrinkage check (sizeof(http_resource) < V1_HTTP_RESOURCE_SIZEOF)
as belt-and-suspenders. The divergence from the task wording is
called out in PERFORMANCE.md "Why not the literal task formulation".

Verified:
- make check: 48/48 PASS, no bench binaries invoked.
- make bench (release -O3): bench_sizeof_http_resource passes static_assert;
  bench_get_headers reports ratio=226-403x (well above 10x threshold).
- make bench (debug -O0 -Werror -Wextra -pedantic): clean compile, ratio=134x.
- cpplint: 0 errors on all new files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses one critical and two major findings from performance-reviewer:

* v1_constants.hpp: select V1_HTTP_RESOURCE_SIZEOF and
  V1_STD_MAP_STRING_BOOL_SIZEOF per stdlib (libstdc++: 56/48, libc++:
  32/24). The previous single-platform literals would have made the
  sizeof static_assert algebra silently incorrect on libstdc++ hosts.
* bench_get_headers.cpp: OUTER 10 -> 11 so samples_ns[OUTER/2] is the
  true median element rather than the lower of the two middle samples.
* measure_v1_get_headers.cpp: pass &value (not value) through the asm
  read-or-memory constraint; required for non-trivial class objects to
  pin the address rather than try to load the whole struct.
* PERFORMANCE.md + bench_sizeof_http_resource.cpp: methodology and
  comments updated to match the new OUTER count and the per-stdlib
  baseline values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…of(http_resource) (PRD-REQ-REQ-001, PRD-REQ-REQ-003, PRD §3.6)
* Replace examples/hello_world.cpp with a 9-LOC lambda demo that
  satisfies the PRD §3.4 acceptance criterion (no http_resource
  subclass, no raw pointer, blocking start(true), <=10 LOC).
* Add examples/shared_state.cpp -- the canonical example of when the
  class form is the right shape: an http_resource holding an int +
  std::mutex shared between render_get and render_post (PRD-HDL-REQ-005,
  AR-006).
* Port the trivial single-method examples (handlers, hello_with_get_arg,
  setting_headers, custom_access_log, minimal_ip_ban, minimal_file_response,
  turbo_mode, daemon_info, minimal_https, minimal_https_psk,
  basic_authentication, digest_authentication, iovec_response_example,
  pipe_response_example, external_event_loop) to on_get / lambda form.
* url_registration: lambda for stateless routes; keep one resource for
  the prefix + regex registration that on_* does not cover.
* centralized_authentication: lambdify the two trivial resources; keep
  the free-function auth_handler.
* Delete examples/minimal_hello_world.cpp -- superseded by the new
  hello_world.cpp (no v1-only files existed; this is an idiom-replacement,
  not v1 cleanup).
* Rewrite examples/README.md as a categorized map of the suite, used
  as the input for TASK-041.
* Add scripts/check-examples.sh -- static guard for hello_world LOC and
  shared_state structure. Wired into the top-level Makefile.am as a new
  check-examples target invoked by check-local, so it runs every
  `make check`.
* Add scripts/verify-installed-examples.sh -- compiles every example
  against an installed prefix (the "consumer view" of the v2.0 headers).

Verified: make check is 48/48 passing; scripts/check-examples.sh and
scripts/verify-installed-examples.sh both green; release and
--enable-debug (-Werror -Wextra) builds of every example are clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round of fixes from the validation loop on top of the initial rewrite:

* Add `override` to every render_* declaration across the resource-form
  examples (allowing_disallowing_methods, args_processing, service,
  file_upload, file_upload_with_callback, deferred_with_accumulator) so
  signature drift is caught at compile time, matching the pattern that
  shared_state.cpp already established.
* deferred_with_accumulator.cpp: switch the closure capture to
  `std::make_shared`, drop the stale `sleep(1)` migration comment, and
  keep the accumulator self-contained.
* binary_buffer_response.cpp: replace 'string_response' references in
  the introductory comments with `http_response::string(...)` so the
  prose matches the v2.0 API.
* centralized_authentication.cpp: clean up the trailing usage block and
  align the env-var pattern with the rest of the auth examples.
* basic_authentication / digest_authentication / minimal_https /
  minimal_https_psk: tighten the v2.0 idiom (smart-ptr ownership,
  consistent `with_*` chains, lambda where the class form added no
  value), document the TLS priority-string choice on the PSK example.
* empty_response_example.cpp / minimal_deferred.cpp / hello_with_get_arg.cpp:
  trim redundant comments and tighten string handling.
* service.cpp / url_registration.cpp / args_processing.cpp /
  file_upload[_with_callback].cpp: drop superfluous includes and
  comments, prefer `std::make_unique` and string assembly without
  per-+ temporaries.

* scripts/check-examples.sh — add `set -eo pipefail`, validate the LOC
  counter, guard the disk-to-Makefile.am glob with `nullglob`, and
  document the `noinst_PROGRAMS` single-line assumption.
* scripts/verify-installed-examples.sh — `trap` cleanup of the mktemp
  prefix, surface make-install stderr on failure, rename the
  hard-abort helper to `fatal`, assert that at least one example was
  compiled so a broken AM_CXXFLAGS parse cannot silent-pass.

* specs/tasks/M6-release/TASK-040.md and specs/tasks/_index.md: mark
  the task Done and tick the action items.

Verified: `make check` 48/48 passing, `scripts/check-examples.sh` and
`scripts/verify-installed-examples.sh` both green, release and
`--enable-debug` builds of every example clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Snapshot of the final validation-loop report — one major and 81 minor
items that were knowingly left for follow-up (cosmetic style nits,
documentation-only examples, ergonomic improvements to the verify
script, and a single architecture finding on
empty_response_example.cpp where the example exposes
`MHD_RF_HEAD_ONLY_RESPONSE`; addressing it cleanly requires a
libhttpserver-native HEAD-only flag that does not yet exist in the
public API). No criticals.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…D §3.4, PRD-HDL-REQ-001..006)

Three commits on task/TASK-040:
1. Rewrite the example suite to v2.0 idioms (`on_get` + lambda where it
   shines, `http_resource` class form with `std::make_unique` where
   shared state belongs in a class), add hello_world.cpp (≤10 LOC) and
   shared_state.cpp as the canonical pair, plus
   scripts/check-examples.sh (wired into `make check`) and
   scripts/verify-installed-examples.sh (compiles every example
   against an installed prefix to validate the consumer view of the
   v2.0 headers).
2. Validation-loop fixes: `override` everywhere, `make_shared`
   consistency, comment hygiene, hardened CI scripts (`set -eo
   pipefail`, mktemp trap cleanup, asserted compile count).
3. Snapshot of the unworked findings (1 major, 81 minor, 0 critical).

Verified: `make check` 48/48 green, `scripts/check-examples.sh` and
`scripts/verify-installed-examples.sh` both green on release and
`--enable-debug` builds of every example.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant